﻿using System.Collections;
using System.Dynamic;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Unicode;

namespace Common.Class
{
    public class JsonHelper
    {

        public JsonHelper()
        {

        }

        public JsonHelper(string js)
        {
            JsonStr = js;
        }

        /// <summary>
        /// 变量JSON字符串
        /// </summary>
        public string JsonStr
        {
            get { return GetJsonStr(); }
            set { SetJsonStr(value); }
        }

        Hashtable ht = new Hashtable();

        /// <summary>
        /// 获取Json字符串所有key
        /// </summary>
        /// <param name="VarName">变量名</param>
        /// <returns></returns>
        public List<string> GetAllKey()
        {
            if (JsonStr.ToLower() == "null")
            {
                return null;
            }
            if (JsonStr == "")
            {
                return null;
            }
            List<string> _keyList = new List<string>();
            foreach (string key in ht.Keys)
            {
                _keyList.Add(key);
            }
            return _keyList;

        }

        /// <summary>
        /// 增加变量
        /// </summary>
        /// <param name="VarName">变量名</param>
        /// <param name="VarValue">变量值</param>
        public JsonHelper AddVar(string VarName, object VarValue)
        {

            if (ht.Contains(VarName))
            {
                ht[VarName] = VarValue;
            }
            else
            {
                ht.Add(VarName, VarValue);
            }
            return this;

        }

        /// <summary>
        /// 获取变量值
        /// </summary>
        /// <param name="VarName">变量名</param>
        /// <returns></returns>
        public string GetVar(string VarName)
        {
            if (JsonStr.ToLower() == "null")
            {
                return null;
            }
            if (JsonStr == "")
            {
                return "";
            }
            if (ht.Contains(VarName))
            {
                if (ht[VarName] != null)
                    return ht[VarName].ToString();
            }
            return null;

        }

        /// <summary>
        /// 清除所有变量
        /// </summary>
        public void Clear()
        {
            ht.Clear();
        }

        /// <summary>
        /// 移除变量
        /// </summary>
        /// <param name="VarName"></param>
        public void RemoveVar(string VarName)
        {
            ht.Remove(VarName);
        }

        /// <summary>
        /// 通过变量表获取JSON字符串
        /// </summary> 
        /// <returns></returns>
        private string GetJsonStr()
        {
            return GetJsonStrFromObject(ht, false);
        }

        /// <summary>
        /// 将JSON字符串转换成变量表
        /// </summary>
        /// <param name="js"></param>
        private void SetJsonStr(string js)
        {
            ht = GetObjectFromJsonStr<Hashtable>(js);
        }



        /// <summary>
        /// 获取对象JSON字符串
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public static string GetJsonStrFromObject(object o, bool indented = false)
        {
            JsonSerializerOptions options = new JsonSerializerOptions
            {
                MaxDepth = 64,//json循环最大深度 如果你所需的资源确实超过了32层，可以加深深度来解决 
                PropertyNamingPolicy = null,//对象属性名不改变大小写
                Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),//取消Unicode编码
                ReadCommentHandling = JsonCommentHandling.Skip,//允许在 JSON 输入中使用注释并忽略它们 
                AllowTrailingCommas = true,//允许额外符号
                PropertyNameCaseInsensitive = false,//反序列化过程中属性名称是否使用不区分大小写的比较
                NumberHandling = JsonNumberHandling.AllowReadingFromString,// 允许在反序列化的时候原本应为数字的字符串（带引号的数字）转为数字
                WriteIndented = indented,// 整齐输出

            };
            options.Converters.Add(new DateTimeConverterUsingDateTimeParse());
            options.Converters.Add(new LongJsonConverter());

            return JsonSerializer.Serialize(o, options);
        }

        /// <summary>
        /// 根据JSON字符串获取对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="s"></param>
        /// <returns></returns>
        public static T GetObjectFromJsonStr<T>(string s)
        {
            if (string.IsNullOrWhiteSpace(s) || s.ToLower() == "null")
            {
                object _o = null;
                return (T)_o;
            }

            JsonSerializerOptions options = new JsonSerializerOptions
            {
                MaxDepth = 64,//json循环最大深度 如果你所需的资源确实超过了32层，可以加深深度来解决 
                PropertyNamingPolicy = null,//对象属性名不改变大小写
                Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),//取消Unicode编码
                ReadCommentHandling = JsonCommentHandling.Skip,//允许在 JSON 输入中使用注释并忽略它们 
                AllowTrailingCommas = true,//允许属性值末尾存在逗号
                PropertyNameCaseInsensitive = false,//反序列化过程中属性名称是否使用不区分大小写的比较
                NumberHandling = JsonNumberHandling.AllowReadingFromString,// 允许在反序列化的时候原本应为数字的字符串（带引号的数字）转为数字
                WriteIndented = true,// 整齐输出
                IgnoreReadOnlyProperties = true,// 忽略只读字段 
            };
            options.Converters.Add(new DateTimeConverterUsingDateTimeParse());
            options.Converters.Add(new LongJsonConverter());

            return JsonSerializer.Deserialize<T>(s, options);
        }

        /// <summary>
        /// 将Json字符串转换为动态类型
        /// </summary>
        /// <param name="json"></param>
        /// <returns></returns>
        public static dynamic JsonToDynamic(string json)
        {
            JsonDocument doc = JsonDocument.Parse(json);
            return JsonElementToDynamic(doc.RootElement);
        }

        private static dynamic JsonElementToDynamic(JsonElement element)
        {
            switch (element.ValueKind)
            {
                case JsonValueKind.Object:
                    dynamic obj = new ExpandoObject();
                    var dict = (IDictionary<string, object>)obj;
                    foreach (JsonProperty property in element.EnumerateObject())
                    {
                        dict[property.Name] = JsonElementToDynamic(property.Value);
                    }
                    return obj;
                case JsonValueKind.Array:
                    var array = new System.Collections.Generic.List<dynamic>();
                    foreach (JsonElement item in element.EnumerateArray())
                    {
                        array.Add(JsonElementToDynamic(item));
                    }
                    return array;
                case JsonValueKind.String:
                    return element.GetString();
                case JsonValueKind.Number:
                    if (element.TryGetInt32(out int intValue))
                    {
                        return intValue;
                    }
                    else if (element.TryGetDouble(out double doubleValue))
                    {
                        return doubleValue;
                    }
                    return null;
                case JsonValueKind.True:
                    return true;
                case JsonValueKind.False:
                    return false;
                case JsonValueKind.Null:
                    return null;
                default:
                    throw new NotSupportedException($"Unsupported JsonValueKind: {element.ValueKind}");
            }
        }

    }


    /// <summary>
    /// 转换时间时控制字符串的格式
    /// </summary>
    public class DateTimeConverterUsingDateTimeParse : JsonConverter<DateTime>
    {
        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                if (DateTime.TryParse(reader.GetString(), out DateTime date))
                    return date;
            }
            return reader.GetDateTime();
        }
        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
        }
    }

    /// <summary>
    /// 转换时将Long类型转换为字符串传给前端，解决前端Js无法处理Long类型的问题
    /// </summary>
    public class LongJsonConverter : JsonConverter<long?>
    {
        public override long? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                if (Int64.TryParse(reader.GetString(), out Int64 date))
                    return date;
            }
            return reader.GetInt64();
        }

        public override void Write(Utf8JsonWriter writer, long? value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(Convert.ToString(value) + "");
        }
    }

    /// <summary>
    /// 解决动态类型转换问题
    /// </summary>
    public class DynamicJsonConverter : JsonConverter<dynamic>
    {
        public override dynamic Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {

            if (reader.TokenType == JsonTokenType.True)
            {
                return true;
            }

            if (reader.TokenType == JsonTokenType.False)
            {
                return false;
            }

            if (reader.TokenType == JsonTokenType.Number)
            {
                if (reader.TryGetInt64(out long l))
                {
                    return l;
                }

                return reader.GetDouble();
            }

            if (reader.TokenType == JsonTokenType.String)
            {
                if (reader.TryGetDateTime(out DateTime datetime))
                {
                    return datetime;
                }

                return reader.GetString();
            }

            if (reader.TokenType == JsonTokenType.StartObject)
            {
                using JsonDocument documentV = JsonDocument.ParseValue(ref reader);
                return ReadObject(documentV.RootElement);
            }
            // Use JsonElement as fallback.
            // Newtonsoft uses JArray or JObject.
            JsonDocument document = JsonDocument.ParseValue(ref reader);
            return document.RootElement.Clone();
        }

        private object ReadObject(JsonElement jsonElement)
        {
            IDictionary<string, object> expandoObject = new ExpandoObject();
            foreach (var obj in jsonElement.EnumerateObject())
            {
                var k = obj.Name;
                var value = ReadValue(obj.Value);
                expandoObject[k] = value;
            }
            return expandoObject;
        }

        private object? ReadValue(JsonElement jsonElement)
        {
            object? result = null;
            switch (jsonElement.ValueKind)
            {
                case JsonValueKind.Object:
                    result = ReadObject(jsonElement);
                    break;
                case JsonValueKind.Array:
                    result = ReadList(jsonElement);
                    break;
                case JsonValueKind.String:
                    //TODO: Missing Datetime&Bytes Convert
                    result = jsonElement.GetString();
                    break;
                case JsonValueKind.Number:
                    //TODO: more num type
                    result = 0;
                    if (jsonElement.TryGetInt64(out long l))
                    {
                        result = l;
                    }
                    break;
                case JsonValueKind.True:
                    result = true;
                    break;
                case JsonValueKind.False:
                    result = false;
                    break;
                case JsonValueKind.Undefined:
                case JsonValueKind.Null:
                    result = null;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            return result;
        }

        private object? ReadList(JsonElement jsonElement)
        {
            IList<object?> list = new List<object?>();
            foreach (var item in jsonElement.EnumerateArray())
            {
                list.Add(ReadValue(item));
            }
            return list.Count == 0 ? null : list;
        }

        public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString());
        }
    }
}
